# -*- coding: utf-8 -*-
# =============================================================================
#     FileName:
#         Desc:
#       Author: GGA
#        Email:
#     HomePage:
#      Version: 1.0.1
#   LastChange: 2020-12-20
#      History:
# =============================================================================
import os
import sys
import subprocess
import logging
import re
import datetime

import fire

from utils.logger_helper import LoggerHelper
from common_config import CommonConfig
from mysql_binlog_parser import MySQLBinlogParser

logger = LoggerHelper.get_logger()


class BinlogBigTranChecker(object):

    @classmethod
    def is_big_tran(cls, start_tran_time, stop_tran_time, start_tran_pos, stop_tran_pos, tran_dml_count):
        if start_tran_time is None or stop_tran_time is None:
            return False
        if start_tran_pos is None or stop_tran_pos is None:
            return False
        if (stop_tran_pos - start_tran_pos) > CommonConfig.BIG_TRAN_BINLOG_SIZE:
            return True
        if (stop_tran_time - start_tran_time).total_seconds() > CommonConfig.BIG_TRAN_EXEC_SECONDS:
            return True
        if tran_dml_count > CommonConfig.BIG_TRAN_DML_COUNT:
            return True
        return False

    @classmethod
    def show_big_trans(cls, big_tran_list):
        if len(big_tran_list) == 0:
            logger.info("未找到满足条件的大事务")
            return
        messages = list()
        messages.append("")
        for big_tran in big_tran_list:
            messages.append("*" * 30)
            messages.append("开始位点:{}".format(big_tran["start_tran_pos"]))
            messages.append("结束位点:{}".format(big_tran["stop_tran_pos"]))
            messages.append("开始时间:{}".format(big_tran["start_tran_time"].strftime("%Y-%m-%d %H:%M:%S")))
            messages.append("结束时间:{}".format(big_tran["stop_tran_time"].strftime("%Y-%m-%d %H:%M:%S")))
            messages.append("BINLOG长度:{}".format(big_tran["stop_tran_pos"] - big_tran["start_tran_pos"]))
            messages.append("DML数量:{}".format(big_tran["tran_dml_count"]))
        logger.info("\r\n".join(messages))

    @classmethod
    def check_binlog(cls, binlog_path):
        if not os.path.exists(binlog_path):
            logger.warning("指定的binlog存在")
            return False
        logger.info("开始解析binlog: {}".format(binlog_path))
        cmd_temp = "{mysqlbinlog_exe} -v --base64-output=DECODE-ROWS '{binlog_path}' |" \
                   "egrep '### INSERT INTO|### UPDATE|### DELETE FROM|# at|" \
                   "thread_id=|Xid ='"
        cmd = cmd_temp.format(
            mysqlbinlog_exe=CommonConfig.MYSQLBINLOG_EXE,
            binlog_path=binlog_path
        )
        parse_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        current_line_num = 0
        current_time = None
        current_pos = 0
        big_tran_list = list()
        start_tran_time = None
        stop_tran_time = None
        stop_tran_pos = None
        start_tran_pos = None
        tran_dml_count = 0
        while True:
            file_bytes = parse_process.stdout.readline()
            if not file_bytes:
                break
            current_line = file_bytes.decode().strip()
            current_line_num += 1
            if current_line_num % 10000 == 0:
                logger.info("当前处理到第{}行".format(current_line_num))
            if current_line.find("# at") >= 0:
                current_pos = MySQLBinlogParser.parse_binlog_pos(current_line)
                continue
            if current_line.find("thread_id=") >= 0:
                start_tran_time = MySQLBinlogParser.parse_binlog_datetime(current_line)
                start_tran_pos = current_pos
                continue
            if current_line.find("Xid =") >= 0:
                stop_tran_time = MySQLBinlogParser.parse_binlog_datetime(current_line)
                stop_tran_pos = current_pos
                if cls.is_big_tran(
                        start_tran_time=start_tran_time, stop_tran_time=stop_tran_time,
                        start_tran_pos=start_tran_pos, stop_tran_pos=stop_tran_pos,
                        tran_dml_count=tran_dml_count):
                    big_tran_info = {
                        "start_tran_time": start_tran_time, "stop_tran_time": stop_tran_time,
                        "start_tran_pos": start_tran_pos, "stop_tran_pos": stop_tran_pos,
                        "tran_dml_count": tran_dml_count
                    }
                    big_tran_list.append(big_tran_info)
                start_tran_time = None
                stop_tran_time = None
                stop_tran_pos = None
                start_tran_pos = None
                tran_dml_count = 0
                continue
            table_name = MySQLBinlogParser.parse_table_name_line(current_line)
            if table_name is not None:
                tran_dml_count += 1
                continue
        logger.info("解析binlog完成")
        cls.show_big_trans(big_tran_list=big_tran_list)


def check_long_tran(binlog_path):
    """

    :param binlog_path: 要解析BINLOG文件路径
    :return:
    """
    LoggerHelper.init_logger()
    BinlogBigTranChecker.check_binlog(binlog_path=binlog_path)


if __name__ == '__main__':
    fire.Fire(check_long_tran)
